// HSS.Net.Mail.EmailAddress.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license
// http://www.opensource.org/licenses/mit-license.html
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       EmailAddress.cs
// Author:     HSS\gbanta
// Created:    08/12/2010
// Modified:   12/04/2010
// ----------------------------------------------------------------------------
namespace HSS.Net.Mail
{
	#region Using Directives
	using System;
	using System.Text.RegularExpressions;
	using System.Collections;
	#endregion

	#region Email Address
	/// <summary>
	/// Parses and Validates an Email Address against an RFC822 RegEx
	/// Adapted from Jeffrey E.F. Friedl's original RegEx Validator
	/// </summary>
	public sealed class EmailAddress
	{
		#region Fields
		// static so that it's shared accross multiple instances.
		private static Regex oRegex = null;

		// Constants to combat backslashitis
		private const string Escape = @"\\";
		private const string Period = @"\.";
		private const string Space = @"\040";
		private const string Tab = @"\t";
		private const string OpenBr = @"\[";
		private const string CloseBr = @"\]";
		private const string OpenParen = @"\(";
		private const string CloseParen = @"\)";
		private const string NonAscii = @"\x80-\xff";
		private const string Ctrl = @"\000-\037";
		private const string CRList = @"\n\015"; // Should only really be \015
		#endregion

		#region Constructors
		/// <summary>
		/// Constructor
		/// </summary>
		public EmailAddress()
		{
			initRegex();
		}
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="eMailAddress">The eMail Address to parse</param>
		public EmailAddress(string eMailAddress)
		{
			initRegex();
			Parse(eMailAddress);
		}

		#endregion

		#region Properties
		/// <summary>
		/// THe loca parts of the Email Address
		/// </summary>
		public string LocalPart
		{
			get { return _localPart; }
		} string _localPart;
		/// <summary>
		/// The domain of the email address
		/// </summary>
		public string Domain
		{
			get { return _domain; }
		} string _domain;
		/// <summary>
		/// Any quoted strings
		/// </summary>
		public string QuotedString
		{
			get { return _quotedStrings; }
		} string _quotedStrings;
		/// <summary>
		/// The mailbox of the email address
		/// </summary>
		public string Mailbox
		{
			get { return _mailBox; }
		} string _mailBox;
		/// <summary>
		/// Is the email address valid
		/// </summary>
		public bool IsValid
		{
			get { return _isValid; }
		} bool _isValid;

		#endregion

		#region Methods
		/// <summary>
		/// Parses and Validates an Email Address against an RFC822 RegEx
		/// </summary>
		/// <param name="eMailAddress">The email address to parse</param>
		/// <returns>true, if the Email Address is Valid; else false</returns>
		/// <remarks>
		/// Adapted from Jeffrey E.F. Friedl's original RegEx Validator
		/// </remarks>
		public bool Parse(string eMailAddress)
		{

			// Validate
			Regex regexEmail = new Regex(@"^[a-zA-Z][\w\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]$", RegexOptions.IgnoreCase);
			Match m = regexEmail.Match(eMailAddress);
			this._isValid = ((m.Length == eMailAddress.Length) && (eMailAddress.Length != 0));

			// Parse
			Match parse = EmailAddress.oRegex.Match(eMailAddress);
			this._domain = parse.Groups["domain"].ToString();
			this._localPart = parse.Groups["localpart"].ToString();
			this._mailBox = "<" + parse.Groups["mailbox"].ToString() + ">";
			this._quotedStrings = parse.Groups["quotedstr"].ToString();

			return this._isValid;
		}

		private void initRegex()
		{
			if (null != EmailAddress.oRegex)
				return;

			// for within "";
			string qtext = @"[^" + EmailAddress.Escape +
				EmailAddress.NonAscii +
				EmailAddress.CRList + "\"]";
			string dtext = @"[^" + EmailAddress.Escape +
				EmailAddress.NonAscii +
				EmailAddress.CRList +
				EmailAddress.OpenBr +
				EmailAddress.CloseBr + "\"]";

			string quoted_pair = " " + EmailAddress.Escape + " [^" + EmailAddress.NonAscii + "] ";

			// *********************************************
			// comments.
			// Impossible to do properly with a regex, I make do by allowing at most 
			// one level of nesting.
			string ctext = @" [^" + EmailAddress.Escape +
				EmailAddress.NonAscii +
				EmailAddress.CRList + "()] ";

			// Nested quoted Pairs
			string Cnested = "";
			Cnested += EmailAddress.OpenParen;
			Cnested += ctext + "*";
			Cnested += "(?:" + quoted_pair + " " + ctext + "*)*";
			Cnested += EmailAddress.CloseParen;

			// A Comment Usually 
			string comment = "";
			comment += EmailAddress.OpenParen;
			comment += ctext + "*";
			comment += "(?:";
			comment += "(?: " + quoted_pair + " | " + Cnested + ")";
			comment += ctext + "*";
			comment += ")*";
			comment += EmailAddress.CloseParen;

			// *********************************************
			// X is optional whitespace/comments
			string X = "";
			X += "[" + EmailAddress.Space + EmailAddress.Tab + "]*";
			X += "(?: " + comment + " [" + EmailAddress.Space + EmailAddress.Tab + "]* )*";

			// an email address atom... it's not nuclear ;)
			string ato_char = @"[^(" + EmailAddress.Space + ")<>\\@,;:\\\"." + EmailAddress.Escape + EmailAddress.OpenBr +
				EmailAddress.CloseBr +
				EmailAddress.Ctrl +
				EmailAddress.NonAscii + "]";
			string atom = "";
			atom += ato_char + "+";
			atom += "(?!" + ato_char + ")";

			// doublequoted string, unrolled.
			string quoted_str = "(?'quotedstr'";
			quoted_str += "\\\"";
			quoted_str += qtext + " *";
			quoted_str += "(?: " + quoted_pair + qtext + " * )*";
			quoted_str += "\\\")";

			// A word is an atom or quoted string
			string word = "";
			word += "(?:";
			word += atom;
			word += "|";
			word += quoted_str;
			word += ")";

			// A domain-ref is just an atom
			string domain_ref = atom;

			// A domain-literal is like a quoted string, but [...] instead of "..."
			string domain_lit = "";
			domain_lit += EmailAddress.OpenBr;
			domain_lit += "(?: " + dtext + " | " + quoted_pair + " )*";
			domain_lit += EmailAddress.CloseBr;

			// A sub-domain is a domain-ref or a domain-literal
			string sub_domain = "";
			sub_domain += "(?:";
			sub_domain += domain_ref;
			sub_domain += "|";
			sub_domain += domain_lit;
			sub_domain += ")";
			sub_domain += X;

			// a domain is a list of subdomains separated by dots
			string domain = "(?'domain'";
			domain += sub_domain;
			domain += "(:?";
			domain += EmailAddress.Period + " " + X + " " + sub_domain;
			domain += ")*)";

			// a a route. A bunch of "@ domain" separated by commas, followed by a colon.
			string route = "";
			route += "\\@ " + X + " " + domain;
			route += "(?: , " + X + " \\@ " + X + " " + domain + ")*";
			route += ":";
			route += X;

			// a local-part is a bunch of 'word' separated by periods
			string local_part = "(?'localpart'";
			local_part += word + " " + X;
			local_part += "(?:";
			local_part += EmailAddress.Period + " " + X + " " + word + " " + X;
			local_part += ")*)";

			// an addr-spec is local@domain
			string addr_spec = local_part + " \\@ " + X + " " + domain;

			// a route-addr is <route? addr-spec>
			string route_addr = "";
			route_addr += "< " + X;
			route_addr += "(?: " + route + " )?";
			route_addr += addr_spec;
			route_addr += ">";

			// a phrase........
			string phrase_ctrl = @"\000-\010\012-\037";

			// Like atom-char, but without listing space, and uses phrase_ctrl.
			// Since the class is negated, this matches the same as atom-char plus space and tab

			string phrase_char = "[^()<>\\@,;:\\\"." + EmailAddress.Escape +
				EmailAddress.OpenBr +
				EmailAddress.CloseBr +
				EmailAddress.NonAscii +
				phrase_ctrl + "]";

			// We've worked it so that word, comment, and quoted_str to not consume trailing X
			// because we take care of it manually
			string phrase = "";
			phrase += word;
			phrase += phrase_char;
			phrase += "(?:";
			phrase += "(?: " + comment + " | " + quoted_str + " )";
			phrase += phrase_char + " *";
			phrase += ")*";

			// A mailbox is an addr_spec or a phrase/route_addr
			string mailbox = "";
			mailbox += X;
			mailbox += "(?'mailbox'";
			mailbox += addr_spec;
			mailbox += "|";
			mailbox += phrase + " " + route_addr;
			mailbox += ")";

			// okay, now setup the object... We'll compile it since this is a rather large (euphemistically 
			// speaking) regex... We also need to IgnorePatternWhitespace unless it's escaped.

			EmailAddress.oRegex = new Regex(mailbox, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
		}
		#endregion
	}

	#endregion
}